package controller;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.file.Path;

import javax.imageio.ImageIO;


import model.Frame;
import model.ID3Tag;
import model.MP3File;

/**
 * a class with static methods, to create instances of MP3File with the information parsed from a mp3 file
 *
 *
 */
public class ID3Parser {
	
	/**
	 * static methods, to create instances of MP3File with the information parsed from a mp3 file
	 *
	 * @param pathToFile represents the path to a mp3 file that should be parsed
	 * @return
	 * @throws IOException on general file io problems
	 * @throws InvalidID3TagException when the mp3 file contains invalid id3 specifications
	 */
	public static MP3File parseMP3File(Path pathToFile) throws IOException, InvalidID3TagException{
		File file = pathToFile.toFile();
		boolean isFile = file.isFile();
		if(!isFile) return null;

        try{
        	FileInputStream inputStream = new FileInputStream(file);         
        	byte fileContent[] = new byte[(int)file.length()];
        	inputStream.read(fileContent);
        	inputStream.close();

        	//get and check the id3 identifier
        	byte id3Identifier[] = new byte[3];
        	id3Identifier[0] = fileContent[0];
        	id3Identifier[1] = fileContent[1];
        	id3Identifier[2] = fileContent[2];
        	String strId3FileContent = new String(id3Identifier);
        	if(!strId3FileContent.equals("ID3")) throw new InvalidID3TagException();

        	//get and check the id3 version
        	byte id3Version[] = new byte[2];
        	id3Version[0] = fileContent[3];
        	id3Version[1] = fileContent[4];
        	if(!(id3Version[0] == 3) || !(id3Version[1] == 0)) throw new InvalidID3TagException();

        	//get id3 flags
        	byte id3Flags[] = new byte[1];
        	id3Flags[0] = fileContent[5];
        	if(!(id3Flags[0] == 0)) throw new InvalidID3TagException();

        	//get the size of the id3 tag
        	byte id3Size[] = new byte[4];
        	id3Size[0] = fileContent[6];
        	id3Size[1] = fileContent[7];
        	id3Size[2] = fileContent[8];
        	id3Size[3] = fileContent[9];
        	
        	int tagSize = 0;
        	tagSize = ID3Parser.signedToUnsigned(id3Size[0]) << 21
    		  		| ID3Parser.signedToUnsigned(id3Size[1]) << 14
    		  		| ID3Parser.signedToUnsigned(id3Size[2]) << 7
    		  		| ID3Parser.signedToUnsigned(id3Size[3]);
        	tagSize += 10;
        	
        	byte[] id3tag = new byte[tagSize];
        	byte[] id3header = new byte[10];
        	System.arraycopy(fileContent, 0, id3header, 0, 10);
        	System.arraycopy(fileContent, 0, id3tag, 0, tagSize);
        	MP3File mp3 = parseID3ByteArray(id3tag,10,tagSize, id3header);
        	mp3.setMp3File(file);
        	
        	return mp3;
        }catch(IOException ioe){
        	System.out.println("Exception while reading the file " + ioe);
        }    
        return null;
	}
	
	/**
	 * 
	 * change data in the MP3File object
	 *
	 * @param mp3 file to be updated
	 * @param key to identify the property that has to be set
	 * @param value to be set
	 */
	private static void setMetaInformationOfMp3File(MP3File mp3, String key, String value){
		if(key.compareTo("TALB") == 0){
			mp3.setAlbum(value);
		}else
		if(key.compareTo("TPE1") == 0){
			mp3.setArtist(value);
		}else
		if(key.compareTo("TIT2") == 0){
			mp3.setTitle(value);
		}else
		if(key.compareTo("TYER") == 0){
			mp3.setYear(Integer.parseInt(value.trim()));
		}
	}
	
	/**
	 * change Image on the MP3File object
	 * 
	 * @param mp3 file to be updated
	 * @param key to identify the property that has to be set
	 * @param data that represent an image
	 */
	private static void setImageInformationOfMp3File(MP3File mp3, String key, byte[] data){
		if(key.compareTo("APIC") == 0){
			InputStream inputStream = new ByteArrayInputStream(data);
			try {
				BufferedImage image = ImageIO.read(inputStream);
				mp3.setCoverImage(image);
				
				if (image == null) {
					System.out.println("image is null");
				}else{
					System.out.println("found image with height: " + image.getHeight());
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 
	 * 
	 * @param id3Bytes array of the complete id3 tag as bytes
	 * @param offset of the relevant tag
	 * @param tagSize
	 * @param header
	 * @return MP3File that has the values read from the id3 tag as properties
	 * @throws UnsupportedEncodingException 
	 */
	private static MP3File parseID3ByteArray(byte[] id3Bytes, int offset, int tagSize, byte[] header) throws UnsupportedEncodingException{
		
		MP3File mp3 = new MP3File();
		mp3.setId3Tag(new ID3Tag(header, tagSize));
	
		/*
		 * read frame title, size and content, repeat with next frame till size is reached or frametitle is 0
		 */
		while(offset < tagSize) {
			
			byte[] frameTitle = new byte[4];
			System.arraycopy(id3Bytes, offset, frameTitle, 0, 4);
			if(frameTitle[0] == 0) {
				break;
			}
			String key = new String(frameTitle);

			int frameSize =   signedToUnsigned(id3Bytes[offset + 4]) << 24
					  		| signedToUnsigned(id3Bytes[offset + 5]) << 16
					  		| signedToUnsigned(id3Bytes[offset + 6]) << 8
					  		| signedToUnsigned(id3Bytes[offset + 7]);
			
			byte [] frame = new byte[frameSize+10];
			System.arraycopy(id3Bytes, offset, frame, 0, frameSize+10);
			
			if(! (key.equals("TALB") || key.equals("TPE1") || key.equals("TIT2") || key.equals("TYER") || key.equals("APIC")) ) {
				mp3.getId3Tag().addOtherFrame(key, new Frame(frameSize+10, frame));
				offset += frameSize+10;
			}
			else {
				
				boolean unicode = frame[10] == 1;
				
				if(key.equals("APIC")){
					int frameOffset = 11;
					while(!(frame[frameOffset] == 0)){
							frameOffset++;
					} //MIME type skipped
					
					frameOffset++;
					frameOffset++;//picture type skipped;
					
					if(unicode) {
						while(!(frame[frameOffset -1] == 0 && frame[frameOffset] == 0)){
							frameOffset++;
						}
						
					}
					else {
						while(!(frame[frameOffset] == 0)){
							frameOffset++;
						}
					} 					
					frameOffset++;//skipped picture description
					byte[] imageData = new byte[frameSize-frameOffset];
					System.arraycopy(frame, frameOffset, imageData, 0, frameSize-frameOffset);
					setImageInformationOfMp3File(mp3, key, imageData);
					
				}
				else {
					String data;
					if(unicode) {
						data = new String(frame, 11, frameSize-1, Charset.forName("UTF-16"));
					}
					else {
						data = new String(frame, 11, frameSize-1, Charset.forName("ISO-8859-1"));
					}
					
					//save the currentMeta in the MP3File
					setMetaInformationOfMp3File(mp3, key, data);	
				}
				mp3.getId3Tag().addUsedFrame(key, new Frame(frameSize+10, frame));
				offset += frameSize+10;
			}
		}
		return mp3;
	}
	
	public static int signedToUnsigned(byte b) {
		return b & 0xFF;
	}
	
	/**
	 * 
	 * @param value
	 * @return byte array with value as 7bit representation
	 */
	public static final byte[] intToByteArrayTag(int value) {
	    return new byte[] {
	            (byte)(value >> 21),
	            (byte)(value >> 14),
	            (byte)(value >> 7),
	            (byte)value};
	}
	/**
	 * 
	 * @param value
	 * @return byte array with value
	 */
	public static final byte[] intToByteArrayFrame(int value) {
	    return new byte[] {
	            (byte)(value >> 24),
	            (byte)(value >> 16),
	            (byte)(value >> 8),
	            (byte)value};
	}
}
